Repository navigation
feat(workflow): add workflow automation support - #10221
amir-samiee wants to merge 16 commits into
Conversation
|
非常感谢 PR,但是暂时还没有考虑合并此功能。 |
|
Thank you very much for your review. Since the request was for some while ago I'm not sure if I'd make the changes you suggested right away; but in case I did, I will absolutely make sure to take the points you made into account. Thanks again for your time. |
|
要大版本升级了,所以计划合并。
|
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved workflow bugs can block saves with duplicate subscription names, incorrectly scope all-group operations, and advance steps before server activation or proxy changes complete.
8 open issues
Notify editors when asynchronously loaded group selections change · New FastRealping test steps complete without testing servers · New Repeated profile lookups make server activation quadratic · New Workflow activation does not await core reload completion · New Duplicate subscription remarks can make workflow saves fail · New All-group steps incorrectly fall back to the selected subscription · New Deduplication reports incorrect counts and return value · New Proxy-change steps complete before the OS proxy changes · New
1 file not reviewed
v2rayN/ServiceLib/Resx/ResUI.Designer.cs: Generated file
What changed in this PR
Adds reusable workflows to automate recurring subscription, server-management, testing, and proxy operations through the shared service layer and both desktop interfaces.
Changes:
- Adds persisted workflow definitions, configurable steps, and sequential execution through a runtime interface.
- Adds workflow management and editing windows in WPF and Avalonia, accessible from the main and profiles views.
- Adds tests for serialization, parameter options, action dispatch, and disabled-step handling.
| File | Description |
|---|---|
v2rayN/v2rayN/Views/WorkflowWindow.xaml.cs |
Binds WPF workflow commands and dialogs. |
v2rayN/v2rayN/Views/WorkflowWindow.xaml |
Defines the WPF workflow manager. |
v2rayN/v2rayN/Views/WorkflowEditWindow.xaml.cs |
Binds WPF workflow editing controls. |
v2rayN/v2rayN/Views/WorkflowEditWindow.xaml |
Defines the WPF step editor. |
v2rayN/v2rayN/Views/ProfilesView.xaml.cs |
Binds the workflow shortcut. |
v2rayN/v2rayN/Views/ProfilesView.xaml |
Adds a workflow toolbar button. |
v2rayN/v2rayN/Views/MainWindow.xaml.cs |
Binds the workflow menu command. |
v2rayN/v2rayN/Views/MainWindow.xaml |
Adds the workflow menu entry. |
v2rayN/v2rayN/Common/SimpleViewLocator.cs |
Registers WPF workflow windows. |
v2rayN/v2rayN.Desktop/Views/WorkflowWindow.axaml.cs |
Binds Avalonia workflow commands and dialogs. |
v2rayN/v2rayN.Desktop/Views/WorkflowWindow.axaml |
Defines the Avalonia workflow manager. |
v2rayN/v2rayN.Desktop/Views/WorkflowEditWindow.axaml.cs |
Binds Avalonia step-editing commands. |
v2rayN/v2rayN.Desktop/Views/WorkflowEditWindow.axaml |
Defines the Avalonia step editor. |
v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml.cs |
Binds the workflow shortcut. |
v2rayN/v2rayN.Desktop/Views/ProfilesView.axaml |
Adds a workflow toolbar button. |
v2rayN/v2rayN.Desktop/Views/MainWindow.axaml.cs |
Binds the workflow menu command. |
v2rayN/v2rayN.Desktop/Views/MainWindow.axaml |
Adds the workflow menu entry. |
v2rayN/v2rayN.Desktop/DesignData/DesignData.cs |
Adds workflow design-time view models. |
v2rayN/v2rayN.Desktop/Common/SimpleViewLocator.cs |
Registers Avalonia workflow windows. |
v2rayN/ServiceLib/ViewModels/WorkflowViewModel.cs |
Manages workflow listing, editing, deletion, and execution. |
v2rayN/ServiceLib/ViewModels/WorkflowRuntime.cs |
Connects workflow actions to application operations. |
v2rayN/ServiceLib/ViewModels/WorkflowRunner.cs |
Registers the runtime and starts workflows. |
v2rayN/ServiceLib/ViewModels/WorkflowEditViewModel.cs |
Manages step editing and workflow saving. |
v2rayN/ServiceLib/ViewModels/SubSettingViewModel.cs |
Publishes server-state changes after subscription deletion. |
v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs |
Adds group-scoped workflow operations and a shortcut command. |
v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs |
Opens workflow management and registers its runtime. |
v2rayN/ServiceLib/Resx/ResUI.resx |
Adds workflow labels and notifications. |
v2rayN/ServiceLib/Resx/ResUI.Designer.cs |
Exposes workflow resource strings. |
v2rayN/ServiceLib/Models/Entities/WorkflowItem.cs |
Defines persisted workflow records. |
v2rayN/ServiceLib/Models/Dto/WorkflowStep.cs |
Defines step data and editor-facing properties. |
v2rayN/ServiceLib/Manager/AppManager.cs |
Creates workflow storage and provides queries. |
v2rayN/ServiceLib/Helper/WorkflowStepOptions.cs |
Defines parameter choices, defaults, and normalization. |
v2rayN/ServiceLib/Handler/WorkflowHandler.cs |
Serializes steps and dispatches workflow actions. |
v2rayN/ServiceLib/Handler/ConfigHandler.cs |
Adds workflow persistence and deletion. |
v2rayN/ServiceLib/Events/AppEvents.cs |
Adds workflow-opening and server-state events. |
v2rayN/ServiceLib/Enums/EWorkflowAction.cs |
Defines supported workflow actions. |
v2rayN/ServiceLib/Enums/EServerSelectType.cs |
Defines first/last server selection. |
v2rayN/ServiceLib/Base/IWorkflowRuntime.cs |
Defines the workflow execution interface. |
v2rayN/ServiceLib.Tests/Handler/WorkflowHandlerTests.cs |
Covers workflow data, options, and dispatch behavior. |
Review effort: Balanced
| /// <summary> | ||
| /// Editor-only group name. <see cref="ViewModels.WorkflowEditViewModel"/> resolves it | ||
| /// back into <see cref="SubId"/> when the workflow is saved. | ||
| /// </summary> | ||
| [JsonIgnore] | ||
| public string? SubDisplay { get; set; } |
| RxSchedulers.MainThreadScheduler.Schedule(() => _ = SetSpeedTestResult(result)); | ||
| await Task.CompletedTask; | ||
| }); | ||
| await _speedtestService.RunLoop(actionType, lstSelected); |
| var ordered = selectType == EServerSelectType.Last | ||
| ? lstModel.OrderByDescending(t => ProfileExManager.Instance.GetSort(t.IndexId)) | ||
| : lstModel.OrderBy(t => ProfileExManager.Instance.GetSort(t.IndexId)); |
| { | ||
| return; | ||
| } | ||
| await SetDefaultServer(target.IndexId); |
| private async Task ResolveStepSubsAsync() | ||
| { | ||
| var subItems = await AppManager.Instance.SubItems() ?? []; | ||
| var map = subItems.ToDictionary(t => t.Remarks, t => t.Id); |
| step.SubId = step.SubDisplay.IsNotEmpty() | ||
| && step.SubDisplay != ResUI.AllGroupServers | ||
| && map.TryGetValue(step.SubDisplay, out var id) | ||
| ? id | ||
| : null; |
| public async Task<int> DeduplicateServers(string subId) | ||
| { | ||
| var count = await profiles.DedupServersAsync(subId); | ||
| NoticeManager.Instance.Enqueue(string.Format(ResUI.RemoveDuplicateServerResult, count, 0)); |
| AppEvents.SysProxyChangeRequested.Publish(type); | ||
| return Task.CompletedTask; |
|
Hello dear @2dust |
Rebasing onto master broke workflow persistence: JsonUtils now resolves types through the source-generated AppJsonContext, and WorkflowItem and WorkflowStep were not registered there. Serialize returned an empty string and Deserialize returned null, so saved workflows silently lost every step. Register both types (and their lists). Co-authored-by: openhands <openhands@all-hands.dev>
The editor loads subscription names asynchronously and assigns SubDisplay after the step grid has already bound its group cell, so a plain auto-property left the cell blank. Back it with a field and raise PropertyChanged, matching the other editable step fields. Co-authored-by: openhands <openhands@all-hands.dev>
SpeedtestService has no FastRealping case, so a workflow step that picked it finished without testing anything. The interactive speedtest command treats FastRealping as Realping; mirror that here so the step and the command behave the same. Co-authored-by: openhands <openhands@all-hands.dev>
SetDefaultServerForGroupAsync called ProfileExManager.GetSort per row, and each call scans the whole ProfileEx bag, so ordering a group was O(n*m) on the UI thread. Build the IndexId-to-Sort map once and order with it. Co-authored-by: openhands <openhands@all-hands.dev>
…roup The editor's 'All groups' choice serializes to an empty SubId, but the runner replaced an empty id with the currently selected subscription, so an all-groups step silently scoped to one group. Pass null through to the runtime (documented as every group) and drop the selected-group fallback, including in the activation and test helpers. The runtime seam and its callers now take a nullable group id; tests cover the all-groups case. Co-authored-by: openhands <openhands@all-hands.dev>
Activation only published ReloadRequested, so the next workflow step ran while the core was still restarting with the previous server. Add an awaitable ReloadAsync hook wired to the main reload, and have the activation path opt into it. The interactive command keeps firing the request and returning, as before. Co-authored-by: openhands <openhands@all-hands.dev>
DedupServersAsync returned the post-dedup total, and the notice filled the removed field with it and hard-coded New to 0, so the message was wrong. Return removed/old/new and pass both totals to the notice. Co-authored-by: openhands <openhands@all-hands.dev>
SetSystemProxy only published an event and returned, so the next workflow step ran before the OS proxy actually changed. Inject the status bar's listener routine (now public) and await it, the same way subscription updates are awaited. Co-authored-by: openhands <openhands@all-hands.dev>
The workflow menu lived inside the Subscription menu, but workflows are an independent feature. Give it its own top-level toolbar menu in both the WPF and Avalonia main windows, matching the other top-level entries. The ProfilesView toolbar button is unchanged. Co-authored-by: openhands <openhands@all-hands.dev>
de77e31 to
7352859
Compare
|
Hi @2dust, both points are addressed:
Note: AI assistance was used for parts of this update. |
| <PathIcon Data="{StaticResource SemiIconPlus}" /> | ||
| </Button.Content> | ||
| </Button> | ||
| <Button |
| /// completion, including the core reload after activation and the OS proxy change, so a | ||
| /// following step never runs against a half-applied state. | ||
| /// </summary> | ||
| public sealed class WorkflowRuntime( |
There was a problem hiding this comment.
WorkflowRuntime.cs 属于服务,请放到 Services中,可以改为 WorkflowRuntimeService
| /// registers it after the main view models are built; a registration failure is | ||
| /// surfaced instead of silently doing nothing. | ||
| /// </summary> | ||
| public sealed class WorkflowRunner |
There was a problem hiding this comment.
WorkflowRunner.cs 属于单例,请放到 Manager 中,可以改为 WorkflowRunnerManager
The workflow menu is now a top-level entry in the main interface, so the profiles toolbar button is a second entry point for the same action. Remove it from both the WPF and the Avalonia profile views, along with the now unused command bindings. Co-authored-by: openhands <openhands@all-hands.dev>
WorkflowRuntime is a service, so move it to Services as WorkflowRuntimeService. WorkflowRunner is a singleton, so move it to Manager as WorkflowRunnerManager. Update the call sites and the log category to the new names. Co-authored-by: openhands <openhands@all-hands.dev>
|
@2dust Done |

Summary
Why?
I found myself doing more or less the same sequence of actions almost every time I needed the program; and sometimes the items of each step (e.g. servers, subscriptions) were as high as I needed to go and come back to check whether the last step has been completed. As these steps were slightly (or occasionally quite) different based on situation/needs, I found no better way to use the already present features of v2rayN in a workflow-style manner
Testing
WorkflowHandlerTestscoverage for workflow behavior.